/*
 * cache.c
 *
 *  Created on: 03/11/2011
 *      Author: alejandro
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "configLoader.h"
#include "list.h"
#include "fat32.h"
#include "cache.h"
#include <errno.h>
#include <time.h>
#include <semaphore.h>
#include <signal.h>

extern tipoConfigPFS configPFS;
extern t_list *fileOpenTable;
extern sem_t cacheSemaforo;

#define cacheDumpPath "../cache_dump.txt"

void agregarTimestamp(FILE *file){
	time_t tiempo = time(0);
	struct tm *tlocal = localtime(&tiempo);
	char hora[20];
	strftime(hora, 20, "20%y.%m.%d %H:%M:%S", tlocal);
	fprintf(file, "Timestamp: %s\n", hora);
}

void cacheDump(int numeroSenial) {
	//if (senial == 0) {
		//senial = 1;
		printf("Iniciando Dump de la Cache\n");
		sem_wait(&cacheSemaforo);
		FILE *file;
		file = fopen(cacheDumpPath, "a+");
		/*if(file == NULL)
		 return NULL;*/

		int file_size = fileio_getfilesize(cacheDumpPath);
		if (file_size == 0)
			fprintf(file, "---------------------------------------\n");
		fseek(file, 0, SEEK_END);
		int numCache, numBloque = 1;
		fileCache *entrada;
		for (numCache = 0; numCache < fileOpenTable->elements_count; numCache++) {
			entrada = (fileCache*) collection_list_get(fileOpenTable, numCache
					+ 1);
			/*if(entrada==NULL)
			 return NULL;*/
			agregarTimestamp(file);
			fprintf(file, "Tamanio de Bloque de Cache: 1kb\n");
			fprintf(file, "Cantidad de Bloques de Cache: %i\n\n",
					configPFS.tamCache / 1024);
			for (numBloque = 1; numBloque <= configPFS.tamCache / 1024; numBloque++) {
				fprintf(file, "Archivo: %s\n", entrada->path);
				fprintf(file, "Contenido de Bloque de Cache %i:\n", numBloque);
				fprintf(file, "%s\n\n", entrada->cache[numBloque].cacheBloque);
			}
			fprintf(file, "---------------------------------------\n");
		}
		fclose(file);
		sem_post(&cacheSemaforo);
	//	senial = 0;
	//}
	//return senial;
}

/*
 * Descripcion: crea la cache cuando abrimos un archivo, la cache va a tener un tamanio establecido
 * en el archivo de configuracion. Si esta desactivada no hacemos el malloc y cache apunta NULL.
 */
void agregarCache(const char *path, infoDir info) {
	sem_wait(&cacheSemaforo);
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	int i;
	fileCache *entrada;
	entrada = (fileCache*) collection_list_find(fileOpenTable, comparar);
	if (entrada == NULL) {
		entrada=malloc(sizeof(fileCache));
		entrada->path = malloc(strlen(path)+1);
		memcpy(entrada->path, path, strlen(path)+1);
		entrada->vecesAbierto = 1;
		entrada->fstClus = info.clusterContenido;
		entrada->fifo=0;
		if (configPFS.tamCache == 0) {
			entrada->cache = NULL;
		} else {
			entrada->cache = malloc(sizeof(bloqueCache) * ((configPFS.tamCache)/ 1024));
			for (i = 0; i < ((configPFS.tamCache) / 1024); i++) {
				entrada->cache[i].cacheBloque = malloc(1024);
				entrada->cache[i].numeroDeBloque = 0;
				entrada->cache[i].bitDeUso = noModificado;
			}
		}
		collection_list_add(fileOpenTable, entrada);
	} else {
		entrada->vecesAbierto++;
	}
	sem_post(&cacheSemaforo);
}

/*
 * Descripcion: libera la cache de un archivo abierto.
 */
int liberarCache(const char* path) {
	sem_wait(&cacheSemaforo);
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache* entrada;
	int i;
	entrada = (fileCache*) collection_list_removeByClosure2(fileOpenTable, comparar); //aca find deja la entrada o la saca?
	if(entrada==NULL)
		return 1;
	if (entrada->vecesAbierto == 1) {
		if (configPFS.tamCache != 0) {
			flushCache(entrada);
			for (i = 0; i < configPFS.tamCache / 1024; i++) {
				free(entrada->cache[i].cacheBloque);
			}
			free(entrada->cache);
		}
		free(entrada);
	} else {
		entrada->vecesAbierto--;
		collection_list_add(fileOpenTable, entrada);
	}
	sem_post(&cacheSemaforo);
	return 0;
}

/*
 * Descripcion: manda a escribir toda la cache de un archivo.
 */
void flushCache(fileCache *entrada) {
	int i;
	for (i = 0; i < configPFS.tamCache / 1024; i++) {
		if (entrada->cache[i].bitDeUso == modificado) {
			mandarAEscribirBloque(entrada->cache[i].numeroDeBloque, entrada->cache[i].cacheBloque);
		}
	}
}

/*
 * Descripcion: reemplaza un bloque leido previamente por otro en la cache, se usa el algoritmo FIFO.
 */
void reemplazarBloqueCache(char *bloque, uint32_t numeroDeBloque, fileCache *entrada){
	if(entrada->cache[entrada->fifo].bitDeUso==modificado)
		mandarAEscribirBloque(entrada->cache[entrada->fifo].numeroDeBloque , entrada->cache[entrada->fifo].cacheBloque);
	memcpy(entrada->cache[entrada->fifo].cacheBloque, bloque, 1024);
	entrada->cache[entrada->fifo].numeroDeBloque = numeroDeBloque;
	entrada->fifo++;
	if(entrada->fifo==configPFS.tamCache/1024)
		entrada->fifo=0;
}

void escribirBloqueCache(char *bloque, uint32_t numeroDeBloque, fileCache *entrada){
	int i;
	for(i=0; i<configPFS.tamCache/1024; i++){
		if(numeroDeBloque==entrada->cache[i].numeroDeBloque){
			memcpy(entrada->cache[i].cacheBloque, bloque, 1024);
			entrada->cache[i].bitDeUso=modificado;
			free(bloque);
			return;
		}
	}
}

/*
 * Descripcion: se busca un bloque en la cache, si lo encuentra lo devuelve si no lo manda a leer y luego lo reemplaza.
 */
char* leerBloqueCache(uint32_t numeroDeBloque, fileCache *entrada){
	char *bloque;
	int i;
	for(i=0;i<configPFS.tamCache/1024;i++){
		if(entrada->cache[i].numeroDeBloque==numeroDeBloque){
			bloque = malloc(1024);
			memcpy(bloque, entrada->cache[i].cacheBloque, 1024);
			return bloque;
		}
	}
	bloque = mandarALeerBloque(numeroDeBloque);
	reemplazarBloqueCache(bloque, numeroDeBloque, entrada);
	return bloque;
}

/*
 * Descripcion: devuelve un buffer de lo pedido.
 * Notas: si hacemos malloc despues tenemos que tirar free en read
 */
char* leerCache(const char *path, off_t offsetByte, size_t size){
	char *buffer, *bufferAux, *bloque;
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache *entrada;
	int i, j=0;
	entrada = (fileCache*) collection_list_find(fileOpenTable, comparar);
	if(entrada==NULL){
		buffer=NULL;
		return buffer;
	}
	uint32_t posicionFAT, offsetNumClus, offsetBloque, offsetDentroClus;

	bufferAux=malloc(size+2*1024);   //como maximo empieza en donde termina un bloque y termina donde empieza un bloque
	posicionFAT = entrada->fstClus;
	offsetNumClus = offsetByte / (BootSector.secPerClus * 512);
	//archivo = clusters {2,5,8,6,3,10} offsetNumClus daria 2 si hay que empezar
	//a leer por el cluster 8.
	for (i = 0; i != offsetNumClus; i++) {	//buscamos el 8
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
	}

	int32_t tamanioRestante=size;
	offsetDentroClus = (offsetByte - offsetNumClus * (BootSector.secPerClus * 512))/1024;
	while (tamanioRestante>0) {//iterar hasta que terminemos de leer
		offsetBloque = fat32_obtenerNumeroDeBloque(posicionFAT);

		while (offsetDentroClus < (BootSector.secPerClus)/2 && tamanioRestante>0) {//iterar hasta q termine el cluster
			bloque = leerBloqueCache(offsetBloque+offsetDentroClus, entrada);
			memcpy(bufferAux + j, bloque, 1024);
			free(bloque);
			j += 1024;
			tamanioRestante -= 1024;
			offsetDentroClus += 1;
		}
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		offsetDentroClus=0;
	}
	uint32_t demas;
	demas=offsetByte - offsetNumClus * (BootSector.secPerClus * 512);
	buffer = malloc(size);
	memcpy(buffer, bufferAux+demas, size);
	free(bufferAux);
	return buffer;
}

/*
 * Descripcion: escribimos los datos en la cache del archivo
 */
int escribirCache(const char *path, const char *buf, size_t size, off_t offset) {
	sem_wait(&cacheSemaforo);
	int comparar(void* dato) {
		return !strcmp(((fileCache*) dato)->path, path);
	}
	fileCache *entrada;
	entrada = (fileCache*) collection_list_find(fileOpenTable, comparar);
	if (entrada == NULL)
		return -1;

	uint32_t offsetNumClus, posicionFAT, offsetDentroClus, offsetBloque;
	offsetNumClus = offset / (BootSector.secPerClus * 512);
	posicionFAT = entrada->fstClus;
	int i;
	for (i = 0; i != offsetNumClus; i++) {
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
	}

	char *bloque;
	uint32_t desplazamiento, corrimiento, sizeACopiar, desplazamientoEnBloque;
	desplazamiento = offset - offsetNumClus * (BootSector.secPerClus * 512);

	offsetDentroClus = desplazamiento/1024;
	desplazamientoEnBloque = desplazamiento - (offsetDentroClus * 1024);  //la division da la parte entera asi que estaria bien
	corrimiento = 0;
	sizeACopiar = 1024 * (offsetDentroClus + 1) - desplazamiento;
	uint32_t bytesPorEscribir = size;

	while(bytesPorEscribir > 0){
		offsetBloque = fat32_obtenerNumeroDeBloque(posicionFAT);

		while(bytesPorEscribir > 0 && offsetDentroClus < (BootSector.secPerClus)/2){
			bloque = leerBloqueCache(offsetBloque + offsetDentroClus, entrada);
			if(corrimiento + sizeACopiar > size){
				sizeACopiar = bytesPorEscribir;
			}
			memcpy(bloque + desplazamientoEnBloque, buf + corrimiento , sizeACopiar);;
			desplazamientoEnBloque = 0;
			corrimiento += sizeACopiar;
			bytesPorEscribir -= sizeACopiar;
			sizeACopiar = 1024;
			escribirBloqueCache(bloque, offsetBloque + offsetDentroClus, entrada);
			offsetDentroClus++;
		}
		posicionFAT = fat32_SiguienteCadenaFat(posicionFAT);
		offsetDentroClus = 0;
	}
	sem_post(&cacheSemaforo);
	return 0;
}
